[Music]
okay hello everyone
mmm so as you can notice this year
unites we have a lot of talk about unity
dots and a firm name is unity ecs and so
this essence is also about a project
more like a case study how what do we
achieve and how do we do it with unity
ECS the project is Metro police traffic
simulator so first I'll talk a bit about
myself and my name is Hyneman and I've
been a software developer with about my
last 10 plus year of gaming industry
experience I've been working in many
game companies for example economy from
Singapore and then I moved to Europe
work on a big point in Germany at the
moment I'm a senior developer
at innoGames based in Hamburg Germany we
are like a company of around phone with
employees and as you can see this is a
mouthful of technology so out just
shorthand is to ECS and so this
technology promised to give you very
high performance and that would enable
you to build a massive scale game world
and all of that is at the moment not
possible with the regular unity that you
used to and unity itself demonstrate
that bye-bye they're very impressive
demos nuria's demo and mega city demos
as you can see from the pictures you see
a lot of soldiers a lot of arrows lots
of buildings lots of flying cars so the
focus of their demos is basically to
deliver their promise about high
performance but massive scale and I
think they deliver that spectacularly
however when you look closely the
gameplay complexity is not their focus
when you look at let's say the the arrow
vol.8 the soldier in the square
formation basically just shoot a square
arrows and also on the megacity the
flying cars kind of following a fixed
predefined patterns without too much bad
Fighting's going on and yeah so as you
can also see the trend that when unity
developed
ECS or dots they they first released a
very early version that that fulfill the
promise of giving a very high
performance and then from there they get
a feedback from developers like us to
improve in terms of gameplay complexity
and a few example about this for example
when they release system state
components it allowed us developers to
detect entity additions and deletions
which is something that very obvious how
we can do in regular unity and around
late last year early this year they
released dynamic buffers which basically
allows you to store data in array form
for entity which is also something that
we take for granted in unity regular so
we can see the patterns here that unity
is yes it's kind of catching up with
unity regular in terms of gameplay
complexity and also during this keynote
you all know that they will convert more
and more of unity feature to dots and so
from the purposes of a game companies
how this cutting-edge technology would
benefit our games looking at this
portfolio for example on the website you
see forge of empire which at the moment
is the biggest browser game in the world
and also has a very successful mobile
version and the one on the right wall of
10m is the one I'm working on and
basically at the entire line of there
our simulation heavy game
with a very deep gameplay mechanic that
would pose a big challenge to unity
easiest and as a company we always try
to look forward to cutting-edge
technology and how that would benefit us
to develop future games or feet or
adding feature into our existing game
the question remains that whether it is
possible to create complex gameplay and
how do we solve common problems in the
easiest way because when unity
introduced
ECS it brings two new things to the
table at once the entity component
system and the data oriented design to
us programmers that is used to the
regular unity and object-oriented way of
programming that's a steep learning
curve common problems like part fighting
or collisions are not trivial in this
year's collision here I didn't I did not
mention about the unity physics but just
normal collision for example if you have
a grid and a field unit moving on the
grid checking collision between them in
unity sense is not trivial and that's
why the traffic simulation project was
born basically as a case study and we
try to push as far as we could in terms
of gameplay complexity and I was
switched to the phone now to show the
result
so let's take a look at a simple
scenario so here we have a small
intersection and the vehicle will spawn
into this system as you can see in this
intersection they the driver they all
behave they drive legally and there's no
collision basically so that's a simple
setup let's take a look at a slightly
more complicated intersection of
multiple ends here and if you pay close
attention the vehicle which will be
allowed to move forward and turn right
and then they can go to the left just
like in real life and then then the
other is just the other road allowed to
enter the traffic let's go into a little
more chaotic scenario here in a
roundabout set of that you can see a
vehicle of a different size enter and
exit the roundabout smoothly and they
can see you this roundabout to go right
go straight go left or make a u-turn
they look so happy driving and as far as
traffic simulation go I think one of the
most complicated scenario is the
butterfly into highway intersection and
from here you can see that when the
traffic coming in take a look
you see the cursor alright take a look
at this area you see some of the vehicle
march end and enter the ramp at in the
highway to move at higher speed and when
they enter around they slow down in this
section there's some collision avoidance
going on and they go all the way to the
other side and as you can also pay
attention here some of the vehicle will
choose too much in some other will go
straight depends on their destination so
in terms of complexity this this project
I think I did quite well however
do we have to trade complexity with with
scalability do we still have the massive
scale similar to unity demos take a look
at a bigger test and you can also notice
that this city loaded in immediately on
this divine this is a 2016 phone and
that's because we resume subs in
streaming I will talk about it later
yeah so we can wait for bit for the
traffic to form
yeah but basically the in my test the
performance is more or less and
bottlenecked by the GPU rather than the
CPU the rendering Tecmo technical cause
here after we wait for where the traffic
will form and yeah rush hours coming
yeah so that is the result of the
project you know switch back to the
slide oh yeah in the asset I get from
the historic Achtung CD so the second
part of the presentation will be about
how we do it and I will go into deeply
into details I will do a small recap
about unity ecs and then go on the
pattering section
handling collision section and talk a
bit about generic parallel programming
and it with questions and comments so as
the name suggests entity component
system has three parts the entity is
basically your game object replacement
it make off your game world contains
components and internal implementation
in unity ICS it's just a single index
number an integer and for component it
store the entity's data and when you
combine all component together it it is
actually the simulation State and this
is also the only place where you can
start actual data here and there's no
logic it's just data internal
implementation it is
as a struct which is a bit different
from the regular shop class you can only
use a simple blue table type like float
int last time I try it doesn't even
support string so it kind of simple in
this term the system is where the
behavior I bring into the simulation so
what the system does is that it process
component and transform them it it does
that by querying the futuring entities
with a specific set of components for
example if we have hitpoint component
attached to entity and the damaged
system can vary or entity with a hit
point in poland and check if it's less
than zero it's going to kill the entity
so that is the entry component system
set up and in unity ECS it is not
required but is encourage you to to do
more parallel programming to to get the
unlocked a full potential and so in the
regular unity world you mostly do
sequential programming on the main
thread let's say this guy is your main
core CPU what it does is that he he will
let's say go to the RAM to fetch some
data and the distance between the RAM in
the CPU is actually quite far compared
to let's say l1 + l2 caches and so in
the main thread
you will basically go multiple places
fetching data and and process them and
put it back to to the place you want in
the parallel world the data as are all
coming in stream and tightly packed
together and they are fit into a bunch
of worker thread that is very efficient
in performing simple tasks at high speed
and at large quantity the input as well
as the output or array of data that you
can fit into more and more worker thread
as you can see the second set up is a
lot more efficient
if that looked kind of complicated you
can just think like KFC it looked
similar so in terms of programming most
of the time will be spent writing these
so-called job and most the most popular
job at least in my project is the I chop
for each with this syntax you can
specify which component you're
interested in iterating in this case
this job is right over our entity that
has both the vehicle and the vehicle
coordinate component and do some
processing on it
you can further declare that some of the
components basically read only this will
help the scheduler to to put your job
into the the right place and avoid race
condition and so that is unity easiest
so how how do I break down the traffic
simulator project into entity component
and system so first we have a bunch of
entity that represent the road lands and
the traffic control point I'll talk
about a traffic control point later and
this entity will be feed into the
traffic control system the output will
be the land state the state of each of
the land whether they are enable or
disable the disabled land does not allow
incoming traffic with this stateful
lands together with the vehicle they are
feed it into the part fighting and cool
collision handling system the output
will be the association of the vehicle
and the road lands and where do they sit
on the land basically it's just a one
dimensional coordinate one single number
so together with the spline and data
because it's the road land they are not
straight line they are a Bezier curve
these data together will convert a one
dimensional coordinate into a 3d
coordinate that you have rotation and
translation that will be fit into the
rendering system basically this is the
whole picture of traffic simulator
project project
and so in this section I will explain
the part cutting system and when I was
doing this project I look up on the
internet there's no part fighting
solution for unity is yes like that I
could find so instead I look at a
sequential algorithm and see which one I
can adapt to be Zeus in a unity easiest
way and so the most popular ones that
most people know is the a star basically
it gives you the path from point A to
point B and the advantage of it is that
it has very low memory footprint all you
need is a closed list and open list and
you do a heuristic out cream on it and
these Lisa can even be reused from one
party class to another so it has very
low memory footprint however it does not
scale well and in the scenario of a
traffic traffic simulation we need a lot
of Pat requests in a high frequency
setup the second algorithm I looked at
is called flow field which is quite
popular in strategy games the pros is
that it's can well it can't give you all
the path from all sources to one single
destination in in one compute session
and however it is not really suitable
for traffic simulation because in in a
hectic traffic setup you have vehicle
heading to different allocation all the
time so it kind of the only choice that
I can take here is the floyd-warshall
algorithm the pros is that it's
instantly fast give you the path for
every single pair of sources and
destination why is it so fast because
basically it compute everything upfront
and started in a huge catch and so out
of these three algorithms only the last
one look suitable for the problems we
have however it it have a big memory
issues first I will recap about this
algorithm let's say we have a network of
n nodes and for this algorithm the
complexity for building the
catch is npower by tree which is very
very expensive the cost of them after
catch memory is n power of choose it's
also expensive and this is the
pseudocode it's actually quite simple to
implement basically just three for loop
and then you get the catch so the
problem here is is the memory and
because n power 2 is quite high it is
actually not suitable for metropolice
set up for example we have so yeah the
obvious solution here is to divide the
city into smaller section let's say
district and then we might further 2/4
and so on let's say we have a 1,000 by
1,000 node Network that's 1 million
that's a lot and if we divide it into
ten section of 100 th the the new cache
size will be just 10% of of the 1000 by
1000 which is a huge safe if we divide
it further we save even more so within
this section let's say we divide two 10s
accent and and we receive a Patrick
Rhett request of the source and
destination that sit in the same section
that is very lucky we can simply just
query the catch and give the answer just
your classic fly wash algorithm directly
the problem now is how do we perform
part fighting between different sections
and to further reduce the memory
problems we can apply some grab size
reduction here as I already said we can
divide the graph into connected section
and we can see from here we have the
orange section or city the red green and
so on and we can also organize them into
hierarchy let's say we have a high speed
highway network with this pink color
here that connect the rest of the city
and
in my implementation I also discovered
that more than half of the connection in
a network basically had just one exit
for example when you see this LAN when
the vehicle leave this land basically
the only line you can enter is the next
one so while this land is necessary to
compute the catch after the cache is
computed we can throw away this this
land out of the catch and thus reduce
the catch a lot here will be an
illustration of how do we perform part
fighting between different sections
let's say we have multiple sections in
the city connected by a highway network
and we try to perform part fighting
between these two orange point so for
for each node in the section I store the
nearest engines and nearest exit because
when we look at this part they have
different address let's say this is
quarter tree and it's quarter to of the
same district they are they belong to a
different quarter and in order to have a
part you have to go up the hierarchy go
to the district and when you add here
the destination is now one level below
which means we need to walk backward
from the destination and using the
nearest engines parameter that we pre
computed and now the two points are on
the same network we can carry the catch
to get the path and that complete in the
full path and this path I think we don't
need to perform in advance of the
movement we can do it step by step for
example here is from another network
immediately you go out of network and
from here and so on so we divide the
cost over time I will give a small demo
now this actually look in the game
all right
also just a very weird looking city but
let's say we try to compute a part from
this point to let's say here as you can
see we have two small network connected
by the bigger road here from this
connection there's no other information
store so basically the only way we can
do is go forward and we are here the red
the pink line represent the nearest exit
and the nearest entrance basically in
order to enter this small section you
have to go through here and in order to
exit it you go through here
so the next point will be to exit it to
go to this point from here you are able
to access this point the path is
available to you in the catch attention
that you will be able to reach this
point which is in turn the nearest
engines of the destination that close
the loop and we have to depart
so that's conclude a part fighting
section we move to a collision handling
and the requirements of the traffic
simulation project is that vehicle must
have variable length and the line
segments also have arrival lengths in
the ratio between the vehicle length and
the length segments can vary which mean
one lens can fit in as many car as
possible and at the same time a very
long bus can sit on top of a lot of line
segments and this requirement basically
break the solution of using dynamic
buffer to star and B let's recap our
dynamic buffer basically it allows you
to store an array per entity however
there is a catch it's not similar to
your normalcy shop list which can grow
over time the dynamic buffer you have to
provide the capacity in advance in this
requirement basically does not allow you
to determine the capacity for example if
we want to store all the vehicle in one
length segment we don't know how many
because the line segment can be as long
as it's 1 2 B of course we have to we
need this requirement that there's no
collision between within the same length
and also no collision when plans are
splitting or merging in the game the
road segments are model as a set of
plans let's take a look at this simple
road segment they have two lanes
basically each of them is a one-way
connections and it's also model as an
entity they have two entity here at U
length segments on the right size you're
more complicated setup we have total of
eight lens in this road segment and so
we go through each of the cases and how
do we ensure that there's no collision
when a vehicle enter a length so the the
basic theory behind
this is that every time a vehicle make a
decision to move forward let's say in a
certain distance X distance this this is
a need to be sure to make sure that
there will be no collision during this
entire travel of the vehicle mainly with
the with the vehicle in front let's look
at this scenario we have a land with a
vehicle on it and this is the the length
of the land we have three other vehicle
so in our system the vehicle will
perform part fighting only when it's
reached the end of the land so at this
moment is truck no this will be the next
land it needs to enter and what it does
that it look at this parameter called
enter space which is the maximum
distance that that vehicle can move
toward us land and once the vehicle made
the movement this pie become the
occlusive space which means no other
vehicle can enter this will come again
later
and despite leave behind it's called it
was vacated space so basically for every
land that has a vacay space greater than
zero it means every vehicle in that in
that lands should be moving forward it's
exact amount of distance and we combine
these parameter that make the land
component they close by inclusive space
not needed because no other vehicle can
enter this area so how do we prevent
collision when a vehicle entering
splitting land basically it exactly the
same because the vehicle when it moves
one land it basically block the other
land so there's no other vehicle can
enter the other lens the more tricky
scenarios is when the lands are merging
and at this point we have to introduce
another system to make sure that there
is no collision between merging lanes
that is the traffic control system that
I mentioned previously if we all come in
multiple faces in each face has three
state
the first step was that all the lenses
merging will be blocked so no vehicle
can enter imagine that this is like a
red light in the traffic light and after
this period of time it will start detect
in vehicle it's similar to the green
light so the vehicle will start to move
in and after a certain period of time
this language block again and this phase
will end the moment the last vehicle
exit the land and we repeat to the next
phase and but this turn I will enable
the land below instead of the one above
and in that set of our intersection will
have a smooth traffic and there so these
are the illustration of some of our
intersection and the traffic control
system basically you can specify which
set of plants should be enabled or
disabled in some certain phases for
example in this set of I if I remember
correctly I have two of these will be
enabled at one point and and and that
will help circulating the traffic around
in this example it look a bit messy but
we can divide it into multiple phases
for example the first phase every
vehicle turning right and moving forward
they are not blocking each other so they
allowed to go and after that they are
blocked
these two are blocked the one going left
will be enabled and after that the other
direction will be enabled so there are
four phases in in this big intersection
yeah so that's how collision handling
was done in the project now I move to
the generate parallel programming and
let's look at this sample code again we
talked about data access here so in
it's setup you have unity easiest job
that iterate on every entity with these
two component vehicle and vehicle
coordinate so these data you have in
this parameter list exclusive data unit
EECS ensure that there is no race
condition happening on this data because
you are iterating on these anything
there's no other job no other worker
thread can access this data alongside
with you so you are free to write and
read from all of these component that
happened in this parameter list
sometimes in order to make things happen
you have to perform random access
reading from so called component data
from entity random access is discouraged
from unity ECS because it kind of
violate with memory locality so it
reduced catch efficiency but there is a
trick to recover that I'll talk about it
later
with this setup if you forget to add
this attribute unity easy as we complain
to you basically you have to add this
attribute and you can only read from
from this share data why is that because
ECS want to help you avoid race
condition because their share data if
you write to it there would be a big
problem
however in order to create a convincing
simulations you need entity to interact
with each other and in that sense a lot
of cases in a lot of the job that is
required for the traffic simulation we
need the share data is writable and in
order to enable that you have to add
this very scary attribute native disable
parallel for restriction and race
condition could happen in this array if
you're not careful I will talk about how
to avoid them in the next slide so
multiple well erased condition could
happen the popular one is that you're
trying to write the same address let's
say we have this line in our job code
that trying to write into an array
element a new value and because this is
parallel programming this line will be
executed on multiple worker thread at
once and in some cases we have to work
of thread writing to the same entity
race condition will occur and you ended
up with unknown value you don't know
whether it's this one or this one
the trick to avoid this is to look at
your simulation at your setup and try to
figure out our one-to-one relationship
that your occlusive data have for
example in the traffic simulation setup
the setup that we designed only one
vehicle can enter a specific length only
one vehicle can exit al and this mean if
you write a job to iterate on every
vehicle you are sure that you can write
into the land that is about to enter or
exit because there's a one-to-one
relationship here there would be no
collision so in order to avoid red
condition you need to figure out all of
these one-to-one relationship in your
data as many as possible the second race
condition is that you read and write at
the same time to the same address and
because things happen in parallel you
don't know whether your read operation
will be done first
on your right correlation will be done
first the value ended up in your
memories will be unknown a very simple
example would be just this line of code
you try to read from this value and
increase it right back and because it's
parallel programming this can happen on
multiple threads at once and you end up
it unknown data the solution would be to
do a buffer state so you read from a
place and you write it to an another
place and at the end of frame you write
back to the correct place either that or
you have to array a and B and the system
will
a boolean flag too to work on a and then
right to be and in the next frame we're
gonna be right back to a that will avoid
this type race condition and throughout
implementation of this project I figure
out this is the process that work for me
so for to write a parallel algorithm
that do not have race condition I first
started writing the pseudocode on paper
for the parallel algorithm and look at
the pseudocode trying to solve all the
race condition because we can just look
at all the assignments or the reading
and see if there is any right condition
that could potentially happen and we
solve it in the pseudocode level after
that I will not just implement it right
away
I will only implement a sequential
system version of that pseudocode
because in in the current set of unity'
easiest writing a parallel version of
the system is a lot more time-consuming
and very hard to refactor because
there's a lot of boilerplate code
luckily in the last just like keynote we
we have less boilerplate and with this
sequential system we iterate until it
works because just like generic game
programming experience your your code
does not work on the first go and you
may miss some of the eight cases so we
need to iterate this sequential version
until it covers all the edge cases and
only then I convert it to a job system
and here a new problem would occur some
of the race condition that you miss in
the pseudocode design process will will
show up here so basically what I do is
absolutely convert it to job system I
let it run for a long amount of time and
after let's say I have the 5-minute
weird things start to happen because of
race condition and in case you have too
many race condition that is not solvable
you can always fit the job into multiple
jobs because rare condition only
happened per jobs
and in terms of component design there's
a dilemma we we never know how granular
it needs to be to design a component in
my case I will use a single few
components as much as possible
for example the land component I
mentioned previously we have a length
length enter space and a vacay space of
three fields I basically as you three
components instead of one component with
three fields why is that because when we
perform a rai instruction to a component
every single fuse is written not just
that field that you want so it it
creates more chances of race condition
you have to check for all the three
fields instead of just to feel you you
want to write all right
so in my experience I think around that
I prefer to use single few components
instead of combining multiple feeling to
one single components because whenever
you make a write operation it's right
into the entire component instead of
each individual field and at the time of
design and implementation we'd never
know which job require which components
until they are all written and cover our
education and work well after that we
can always combine the components that
always go together why do we do this
because syntax wise and unity is yes
I'll give you a lot of support if you
write a job which five I remember
correctly only five components or less
if you need more than that you have to
write a lot of stuff yourself medic
whirring and everything and another tips
was to avoid the command buffer as much
as possible and so command buffer is
used for structural changes because in
in the job that you write they they will
iterate through the entities a lot and
that's why it needs the data layer need
to be very efficient and whenever you
want to add an entity you want to add a
component remove composure
it changed the structure of this very
efficient data layout and that we need
to go into a buffer to be performing at
the end of the frame however we might
fall into a pit for of writing a very
complicated parallel jobs just to
populate this command buffer and this
command buffer is executed sequentially
anyway so we shouldn't do to this in the
first place and unity ICS also give you
a lot of batch operation that can be
performed sequentially that's also very
efficient for example if you want to add
components you can do a batch operation
from the entity manager leave it at the
default values in your job to set a
component values in this way you avoid
the command buffer altogether and you
should you sub C in for fastest in
loading as you can see in the demo the
big city was was loaded in immediately
and you can look at the mega city
project to see how it's implemented and
we also override the game object
conversion system to do the fly while
cache computation offline so even with a
big city you can start right away
because the catch is already stored in
entity
and as I talked previously with you a
lot of random access through component
data by entity and that defeat the catch
efficiency however you can gain that
back by using share component to group
entity that is related for example
during part fighting from when we
iterate from one lane we do a random
access to another land
however with you share component to
group all the land within the same
section together so the part fighting in
the pattering setting we only look to
the lens that is close to physically
close to your current line and with this
share component we don't perform any
logic on it but it helped to group this
entity into the same chunk and that fit
into your catch and in this in this way
you still have catch efficiency at the
same time as random access memory and at
first when I write the traffic
simulation as a floating point and
that's a very big mistake so race
condition avoidance the method that I
use have strong assumption on on
one-to-one a closet.if 'ti and those
assumption do not hold when our floating
point value accumulate error over time
so I think my suggestion would be to
write everything using integer instead
of floating point yeah so that's our
presentation it mister time for
questions
[Applause]
and so if you don't use the the scary
attribute that I mentioned in slide
there will be no rail condition because
you only read from the shared data
either that or you you operate on
occlusive data that there's no read
condition whatsoever that unity says
provide you with so in my case I
deliberately access the share data and
enable right into it and in order to do
this we we need to be fully aware that
race condition could happen and my
method would be to look at your data set
of look at a whatever information you
have in your problem see the strong
one-to-one connection between your data
for example in my case the vehicle one
vehicle can only enter one specific land
at a time two vehicle cannot enter one
length so that's because strong
one-to-one connection and when you
iterate on the vehicle you are freely
able to write new state in the
connection that is about to enter and
there will be no no race condition or
car because of this one-to-one
correction
so that we need to check with my company
[Music]
any other question thank you a lot
[Music]